package com.hongyi.system.service.impl;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.hongyi.common.framework.constant.Constants;
import com.hongyi.common.framework.constant.HttpStatus;
import com.hongyi.common.framework.constant.CacheConstants;
import com.hongyi.common.framework.domain.LoginInfoEvent;
import com.hongyi.common.framework.domain.LoginUser;
import com.hongyi.common.framework.domain.RoleData;
import com.hongyi.common.framework.exception.ServiceException;
import com.hongyi.common.framework.utils.DateUtils;
import com.hongyi.common.framework.utils.common.ObjectUtils;
import com.hongyi.common.framework.utils.ServletUtils;
import com.hongyi.common.framework.utils.ip.AddressUtils;
import com.hongyi.common.framework.utils.ip.IpUtils;
import com.hongyi.common.framework.utils.spring.SpringUtils;
import com.hongyi.common.redis.utils.RedisUtils;
import com.hongyi.common.saToken.utils.LoginUtils;
import com.hongyi.system.domain.vo.SysUserVo;
import com.hongyi.system.service.IAuthService;
import com.hongyi.system.service.ISysMenuService;
import com.hongyi.system.service.ISysRoleService;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;


import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@Slf4j
@Service
public class AuthServiceImpl implements IAuthService {

    @Autowired
    private ISysRoleService sysRoleService;

    @Autowired
    private ISysMenuService sysMenuService;

    //登录失败最大尝试次数
    @Value("${verify.maxRetryCount}")
    private Integer maxRetryCount;

    //登录失败最大尝试次数
    @Value("${verify.lockTime}")
    private Integer lockTime;

    /**
     * 登录校验
     */
    @Override
    public void checkLogin(String username, Supplier<Boolean> supplier) {
        String errKey = CacheConstants.PWD_ERR_CNT_KEY + username;
        //获取登录失败校验次数
        Integer errNumber = ObjectUtils.defaultIfNull(RedisUtils.getCacheObject(errKey), 0);
        // 超过登录失败最大尝试次数,直接抛出异常,锁定登录
        if (errNumber >= maxRetryCount) {
            recordLoginInformation(username, Constants.LOGIN_FAIL, "验证错误次数超限,账号已锁定");
            throw new ServiceException("验证错误次数超限,账号锁定2分钟", HttpStatus.ERROR);
        }
        if (supplier.get()) {
            //错误次数递增
            errNumber++;
            //缓存登录失败校验次数
            RedisUtils.setCacheObject(errKey, errNumber, lockTime, TimeUnit.MINUTES);
            // 递增后超过登录失败最大尝试次数,直接抛出异常,锁定登录
            if (errNumber >= maxRetryCount) {
                recordLoginInformation(username, Constants.LOGIN_FAIL, "验证错误次数超限,账号已锁定");
                throw new ServiceException("验证错误次数超限,账号锁定2分钟", HttpStatus.ERROR);
            } else {//抛出密码错误异常
                recordLoginInformation(username, Constants.LOGIN_FAIL, "验证错误");
                throw new ServiceException("验证错误,请重新输入", HttpStatus.ERROR);
            }
        }
        //规定次数内完成密码验证,删除之前失败认证的key
        RedisUtils.deleteObject(errKey);
    }

    /**
     * 记录系统登录信息
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     */
    public void recordLoginInformation(String username, String status, String message) {
        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        //获取IP地址
        String ip = IpUtils.getIpAddr();
        //获取IP归属地址
        String address = AddressUtils.getRealAddressByIP(ip);
        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();
        LoginInfoEvent loginInfoEvent = new LoginInfoEvent();
        loginInfoEvent.setUserName(username);
        loginInfoEvent.setStatus(status);
        loginInfoEvent.setMsg(message);
        loginInfoEvent.setDeviceType("pc");
        loginInfoEvent.setOs(os);
        loginInfoEvent.setBrowser(browser);
        loginInfoEvent.setIpaddr(ip);
        loginInfoEvent.setLoginLocation(address);
        loginInfoEvent.setLoginTime(DateUtils.getNowDate());
        //发布给登录事件记录的监听方法
        SpringUtils.context().publishEvent(loginInfoEvent);
    }


    /**
     * 构建登录用户
     *
     * @param sysUserVo  用户信息
     * @param deviceType
     */
    @Override
    public LoginUser buildLoginUser(SysUserVo sysUserVo, String deviceType) {
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(sysUserVo.getUserId());
        loginUser.setDeptId(sysUserVo.getDeptId());
        loginUser.setUsername(sysUserVo.getUserName());
        loginUser.setNickname(sysUserVo.getUserName());
        loginUser.setAvatar(sysUserVo.getAvatar());
        loginUser.setUserType(sysUserVo.getUserType());
        loginUser.setDeviceType(deviceType);
        //查询系统用户的菜单角色权限
        Set<String> menuPermission = sysMenuService.getMenuPermission(sysUserVo.getUserId());
        loginUser.setMenuPermission(menuPermission);
        //查询系统用户的相关角色权限
        Set<String> rolePermission = sysRoleService.getRolePermission(sysUserVo.getUserId());
        loginUser.setRolePermission(rolePermission);
        loginUser.setDeptName(ObjectUtil.isNull(sysUserVo.getDept()) ? "" : sysUserVo.getDept().getDeptName());
        List<RoleData> roles = BeanUtil.copyToList(sysUserVo.getRoles(), RoleData.class);
        loginUser.setRoles(roles);
        return loginUser;
    }

    /**
     * 退出登录
     */
    @Override
    public void logout() {
        try {
            //记录退出登录事件
            recordLoginInformation(LoginUtils.getUserName(), Constants.LOGOUT, "退出登录");
        } catch (NotLoginException ignored) {
            log.info("退出登录异常信息：{}", ignored);
        } finally {
            LoginUtils.logout();
        }
    }
}
